/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class UniPoker {
    public static int UNIPOKER_59864 = 59864;
    public static int UNIPOKER_120476 = 120476;
    public static int UNIPOKER_101226 = 101226;
    public static int UNIPOKER_11359 = 11359;
    public static int UNIPOKER_5083 = 5083;
    public static int UNIPOKER_982 = 982;
    public static int UNIPOKER_456 = 456;
    public static int UNIPOKER_370 = 370;
    public static int UNIPOKER_45 = 45;
    public static int UNIPOKER_3 = 3;
 
    public static int UNIPOKER_60020 = 60020;
    public static int UNIPOKER_120166 = 120166;
    public static int UNIPOKER_101440 = 101440;
    public static int UNIPOKER_11452 = 11452;
    public static int UNIPOKER_5096 = 5096;
    public static int UNIPOKER_942 = 942;
    public static int UNIPOKER_496 = 496;
    public static int UNIPOKER_333 = 333;
    public static int UNIPOKER_67 = 67;
    public static int UNIPOKER_8 = 8;
 
    public static int UNIPOKER_60065 = 60065;
    public static int UNIPOKER_120262 = 120262;
    public static int UNIPOKER_101345 = 101345;
    public static int UNIPOKER_11473 = 11473;
    public static int UNIPOKER_5093 = 5093;
    public static int UNIPOKER_941 = 941;
    public static int UNIPOKER_472 = 472;
    public static int UNIPOKER_335 = 335;
    public static int UNIPOKER_76 = 76;
 
    public static int UNIPOKER_60064 = 60064;
    public static int UNIPOKER_120463 = 120463;
    public static int UNIPOKER_101218 = 101218;
    public static int UNIPOKER_11445 = 11445;
    public static int UNIPOKER_5065 = 5065;
    public static int UNIPOKER_938 = 938;
    public static int UNIPOKER_446 = 446;
    public static int UNIPOKER_364 = 364;
    public static int UNIPOKER_58 = 58;
 
    public static int UNIPOKER_0XF = 0xf;
    public static int UNIPOKER_2000 = 2000;
    public static int UNIPOKER_1000 = 1000;
    public static int UNIPOKER_120 = 20;
    public static int UNIPOKER_20 = 20;
    public static int UNIPOKER_16 = 16;
    public static int UNIPOKER_12 = 12;
    public static int UNIPOKER_10 = 10;
    public static int UNIPOKER_6 = 6;
    public static int UNIPOKER_5 = 5;
    public static int UNIPOKER_4 = 4;
    public static int UNIPOKER_2 = 2;
 
    public static int UNIPOKER_0X900000 = 0x900000;
    public static int UNIPOKER_0X800000 = 0x800000;
    public static int UNIPOKER_0X700000 = 0x700000;
    public static int UNIPOKER_0X600000 = 0x600000;
    public static int UNIPOKER_0X500000 = 0x500000;
    public static int UNIPOKER_0X400000 = 0x400000;
    public static int UNIPOKER_0X300000 = 0x300000;
    public static int UNIPOKER_0X200000 = 0x200000;
    public static int UNIPOKER_0X100000 = 0x100000;

    class ArkTools {
        public static long timeInUs() {
            return System.nanoTime();
        }
    }

    static class PlayerExpectation {
        private final int wins;
        private final int[] handTypeCounts;
        /*
         * @State
         */
        PlayerExpectation(int wins, int[] handTypeCounts) {
            this.wins = wins;
            this.handTypeCounts = handTypeCounts;
        }

        void validate(Player player) {
            if (player.wins() != this.wins) {
                throw new RuntimeException("Expected " + player.name() + " to have " + this.wins + ", but they have " + player.wins());
            }

            int[] actualHandTypeCounts = player.handTypeCounts();
            if (this.handTypeCounts.length != actualHandTypeCounts.length) {
                throw new RuntimeException("Expected " + player.name() + " to have " + this.handTypeCounts.length + " hand types, but they have " + actualHandTypeCounts.length);
            }

            for (int handTypeIdx = 0; handTypeIdx < this.handTypeCounts.length; handTypeIdx++) {
                if (this.handTypeCounts[handTypeIdx] != actualHandTypeCounts[handTypeIdx]) {
                    throw new RuntimeException(
                    	"Expected " +
                        player.name() +
                        " to have " +
                        this.handTypeCounts[handTypeIdx] +
                        " " +
                        PlayerExpectation.handTypes[handTypeIdx] +
                        " hands, but they have " +
						actualHandTypeCounts[handTypeIdx]
                    );
                }
            }
        }

        private static final String[] handTypes = {
                "High Cards",
                "Pairs",
                "Two Pairs",
                "Three of a Kinds",
                "Straights",
                "Flushes",
                "Full Houses",
                "Four of a Kinds",
                "Straight Flushes",
                "Royal Flushes"
        };
    }

    static ArrayList<PlayerExpectation> playerExpectations = new ArrayList<>(){{
        add(
			new PlayerExpectation(UNIPOKER_59864, new int[]{
            UNIPOKER_120476,
            UNIPOKER_101226,
            UNIPOKER_11359,
            UNIPOKER_5083,
            UNIPOKER_982,
            UNIPOKER_456,
            UNIPOKER_370,
            UNIPOKER_45,
            UNIPOKER_3,
            0
        	})
		);
        add(
			new PlayerExpectation(UNIPOKER_60020, new int[]{
            UNIPOKER_120166,
            UNIPOKER_101440,
            UNIPOKER_11452,
            UNIPOKER_5096,
            UNIPOKER_942,
            UNIPOKER_496,
            UNIPOKER_333,
            UNIPOKER_67,
            UNIPOKER_8,
            0
        	})
		);
        add(
			new PlayerExpectation(UNIPOKER_60065, new int[]{
            UNIPOKER_120262,
            UNIPOKER_101345,
            UNIPOKER_11473,
            UNIPOKER_5093,
            UNIPOKER_941,
               UNIPOKER_472,
            UNIPOKER_335,
            UNIPOKER_76,
            UNIPOKER_3,
            0
        	})
		);
        add(
			new PlayerExpectation(UNIPOKER_60064, new int[]{
            UNIPOKER_120463,
            UNIPOKER_101218,
            UNIPOKER_11445,
            UNIPOKER_5065,
            UNIPOKER_938,
            UNIPOKER_446,
            UNIPOKER_364,
            UNIPOKER_58,
            UNIPOKER_3,
            0
        	})
		);
    }};

    static class CardDeck {
        private static ArrayList<String> cards = new ArrayList<>();
        /*
         * @State
         */
        public CardDeck() {
            newDeck();
        }

        static void newDeck() {
            // Make a shallow copy of a new deck
            cards = new ArrayList<>(Arrays.asList(CardDeck.newDeck));
        }

        void shuffle() {
            newDeck();

            for (int index = 52; index != 0; ) {
                // Select a random card
                int randomIndex = (int) (Math.random() * index);
                index -= 1;

                String tempCard = this.cards.get(index);
                cards.set(index, this.cards.get(randomIndex));
                cards.set(randomIndex, tempCard);
            }
        }
		// dealOneCard
        String dealOneCard() {
            return this.cards.remove(0);
        }

        static int cardRank(String card) {
            // This returns a numeric value for a card.
            // Ace is highest.

            int rankOfCard = card.codePointAt(0) & UNIPOKER_0XF;
            if (rankOfCard == 0x1) {
                // Make Aces higher than Kings
                return UNIPOKER_0XF;
            }
            return rankOfCard;
        }

        static String cardName(String card) {
            if (card instanceof String) {
                int result = card.codePointAt(0);
                return CardDeck.rankNames[result & UNIPOKER_0XF];
            }
            return "";
        }

        private static final String[] rankNames = {"", "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "", "Queen", "King"};
        private static final String[] newDeck = {
                // Spades
                "\uD83C\uDCA1",
                "\uD83C\uDCA2",
                "\uD83C\uDCA3",
                "\uD83C\uDCA4",
                "\uD83C\uDCA5",
                "\uD83C\uDCA6",
                "\uD83C\uDCA7",
                "\uD83C\uDCA8",
                "\uD83C\uDCA9",
                "\uD83C\uDCAA",
                "\uD83C\uDCAB",
                "\uD83C\uDCAD",
                "\uD83C\uDCAE",
                // Hearts
                "\uD83C\uDCB1",
                "\uD83C\uDCB2",
                "\uD83C\uDCB3",
                "\uD83C\uDCB4",
                "\uD83C\uDCB5",
                "\uD83C\uDCB6",
                "\uD83C\uDCB7",
                "\uD83C\uDCB8",
                "\uD83C\uDCB9",
                "\uD83C\uDCBA",
                "\uD83C\uDCBB",
                "\uD83C\uDCBD",
                "\uD83C\uDCBE",
                // Clubs
                "\uD83C\uDCD1",
                "\uD83C\uDCD2",
                "\uD83C\uDCD3",
                "\uD83C\uDCD4",
                "\uD83C\uDCD5",
                "\uD83C\uDCD6",
                "\uD83C\uDCD7",
                "\uD83C\uDCD8",
                "\uD83C\uDCD9",
                "\uD83C\uDCDA",
                "\uD83C\uDCDB",
                "\uD83C\uDCDD",
                "\uD83C\uDCDE",
                // Diamonds
                "\uD83C\uDCC1",
                "\uD83C\uDCC2",
                "\uD83C\uDCC3",
                "\uD83C\uDCC4",
                "\uD83C\uDCC5",
                "\uD83C\uDCC6",
                "\uD83C\uDCC7",
                "\uD83C\uDCC8",
                "\uD83C\uDCC9",
                "\uD83C\uDCCA",
                "\uD83C\uDCCB",
                "\uD83C\uDCCD",
                "\uD83C\uDCCE"
        };
    }
    /*
     * @Generator
     */
    static class Hand {
        int mRank = 0;
        ArrayList<String> cards = new ArrayList<>();
        /*
         * @State
         */
        public Hand() {
            this.clear();
        }
        void clear() {
        	this.cards = new ArrayList<>();
           	this. mRank = 0;
        }

        void takeCard(String card) {
            this.cards.add(card);
        }
        public void remainingCard(MatchResult secondOfAKindResult, String firstOfAKind, String handString) {
            String secondOfAKind = secondOfAKindResult.group(0);
            // Three of a Kinds + Pairs, or Pairs + Three of a Kinds
            if (
				(firstOfAKind.length() == UNIPOKER_6 && secondOfAKind.length() == UNIPOKER_4) ||
				(firstOfAKind.length() == UNIPOKER_4 && secondOfAKind.length() == UNIPOKER_6)
			) {
                int threeOfAKindCardRank = 0;
                int twoOfAKindCardRank = 0;

                if (firstOfAKind.length() == UNIPOKER_6) {
                    threeOfAKindCardRank = CardDeck.cardRank(firstOfAKind.substring(0, UNIPOKER_2));
                    twoOfAKindCardRank = CardDeck.cardRank(secondOfAKind.substring(0, UNIPOKER_2));
                } else {
                    threeOfAKindCardRank = CardDeck.cardRank(secondOfAKind.substring(0, UNIPOKER_2));
                    twoOfAKindCardRank = CardDeck.cardRank(firstOfAKind.substring(0, UNIPOKER_2));
                }
                this.mRank =
					Hand.fullHouse |
                    (threeOfAKindCardRank << UNIPOKER_16) |
					(threeOfAKindCardRank << UNIPOKER_12) |
					(threeOfAKindCardRank << UNIPOKER_8) |
					(twoOfAKindCardRank << UNIPOKER_4) |
					twoOfAKindCardRank;
            } else if (firstOfAKind.length() == UNIPOKER_4 && secondOfAKind.length() == UNIPOKER_4) {
                int firstPairCardRank = CardDeck.cardRank(firstOfAKind.substring(0, UNIPOKER_2));
                int secondPairCardRank = CardDeck.cardRank(secondOfAKind.substring(0, UNIPOKER_2));
                int otherCardRank = 0;
                // Due to sorting, the other card is at index 0, 4 or 8
                if (firstOfAKind.codePointAt(0) == handString.codePointAt(0)) {
                    if (secondOfAKind.codePointAt(0) == handString.codePointAt(UNIPOKER_4)) {
                        otherCardRank = CardDeck.cardRank(handString.substring(UNIPOKER_8, UNIPOKER_10));
                    } else {
                        otherCardRank = CardDeck.cardRank(handString.substring(UNIPOKER_4, UNIPOKER_6));
                    }
                } else {
                    otherCardRank = CardDeck.cardRank(handString.substring(0, UNIPOKER_2));
                }
				this.mRank =
					Hand.twoPair |
					(firstPairCardRank << UNIPOKER_16) |
					(firstPairCardRank << UNIPOKER_12) |
					(secondPairCardRank << UNIPOKER_8) |
					(secondPairCardRank << UNIPOKER_4) |
					otherCardRank;
            }
        }
        // When comparing lengths, a matched unicode character has a length of 2.
        // Therefore expected lengths are doubled, e.g a pair will have a match length of 4.
        public void comparing(MatchResult ofAKindResult, String handString) {
            if (ofAKindResult.group(0).length() == UNIPOKER_8) {
                mRank = Hand.fourOfAKind | CardDeck.cardRank(cards.get(0));
            } else {
                // Found pair or three of a kind.  Check for two pair or full house.
                String firstOfAKind = ofAKindResult.group(0);
                int remainingCardsIndex = handString.indexOf(firstOfAKind) + firstOfAKind.length();
                Matcher secondOfAKindResult = Pattern.compile(String.valueOf(Hand.ofAKindRegExp)).matcher(handString.substring(remainingCardsIndex));
                if (remainingCardsIndex <= UNIPOKER_6 && secondOfAKindResult.find()) {
                    this.remainingCard(secondOfAKindResult, firstOfAKind, handString);
                } else {
                    int ofAKindCardRank = CardDeck.cardRank(firstOfAKind.substring(0, UNIPOKER_2));
                    int otherCardsRank = 0;
                    for (String card : this.cards) {
                        int cardRank = CardDeck.cardRank(card);
                        if (cardRank != ofAKindCardRank) {
                            otherCardsRank = (otherCardsRank << UNIPOKER_4) | cardRank;
                        }
                    }
                    if (firstOfAKind.length() == UNIPOKER_6) {
                        this.mRank =
							Hand.threeOfAKind | (ofAKindCardRank << UNIPOKER_16) | (ofAKindCardRank << UNIPOKER_12) | (ofAKindCardRank << UNIPOKER_8) | otherCardsRank;
                    } else {
                        this.mRank = Hand.pair | (ofAKindCardRank << UNIPOKER_16) | (ofAKindCardRank << UNIPOKER_12) | otherCardsRank;
                    }
                }
            }
        }
        void score() {
			// Sort highest rank to lowest
            cards.sort((a, b) ->
            	CardDeck.cardRank(b) - CardDeck.cardRank(a)
            );

            String handString = String.join("", this.cards);

            Matcher flushResult = Hand.flushRegExp.matcher(handString);
            Matcher straightResult = Hand.straightRegExp.matcher(handString);
            Matcher ofAKindResult = Hand.ofAKindRegExp.matcher(handString);

            if (flushResult.find()) {
                if (straightResult.find()) {
                    if (straightResult.group(1) != null){
                        this.mRank = Hand.royalFlush;
                    } else {
                        this.mRank = Hand.straightFlush;
                    }
                } else {
                    this.mRank = Hand.flush;
                }
                this.mRank |= (CardDeck.cardRank(this.cards.get(0)) << UNIPOKER_16) | (CardDeck.cardRank(this.cards.get(1)) << UNIPOKER_12);
            } else if (straightResult.find()) {
                this.mRank = Hand.straight | (CardDeck.cardRank(this.cards.get(0)) << UNIPOKER_16) | (CardDeck.cardRank(this.cards.get(1)) << UNIPOKER_12);
            } else if (ofAKindResult.find()) {
                comparing(ofAKindResult, handString);
            } else {
                this.mRank = 0;
                for (String card : this.cards) {
                    int cardRank = CardDeck.cardRank(card);
                    this.mRank = (this.mRank << UNIPOKER_4) | cardRank;
                }
            }
        }
        int getRank() {
            return this.mRank;
        }

        public String toString() {
            return String.join("", this.cards);
        }

        public static final Pattern flushRegExp = Pattern.compile(
                "(\\uD83C\\uDCA1|\\uD83C\\uDCA2|\\uD83C\\uDCA3|\\uD83C\\uDCA4|\\uD83C\\uDCA5|\\uD83C\\uDCA6|\\uD83C\\uDCA7|\\uD83C\\uDCA8|\\uD83C\\uDCA9|\\uD83C\\uDCAA|\\uD83C\\uDCAB|\\uD83C\\uDCAD|\\uD83C\\uDCAE){5}|" +
                        "(\\uD83C\\uDCB1|\\uD83C\\uDCB2|\\uD83C\\uDCB3|\\uD83C\\uDCB4|\\uD83C\\uDCB5|\\uD83C\\uDCB6|\\uD83C\\uDCB7|\\uD83C\\uDCB8|\\uD83C\\uDCB9|\\uD83C\\uDCBA|\\uD83C\\uDCBB|\\uD83C\\uDCBD|\\uD83C\\uDCBE){5}|" +
                        "(\\uD83C\\uDCD1|\\uD83C\\uDCD2|\\uD83C\\uDCD3|\\uD83C\\uDCD4|\\uD83C\\uDCD5|\\uD83C\\uDCD6|\\uD83C\\uDCD7|\\uD83C\\uDCD8|\\uD83C\\uDCD9|\\uD83C\\uDCDA|\\uD83C\\uDCDB|\\uD83C\\uDCDD|\\uD83C\\uDCDE){5}|" +
                        "(\\uD83C\\uDCC1|\\uD83C\\uDCC2|\\uD83C\\uDCC3|\\uD83C\\uDCC4|\\uD83C\\uDCC5|\\uD83C\\uDCC6|\\uD83C\\uDCC7|\\uD83C\\uDCC8|\\uD83C\\uDCC9|\\uD83C\\uDCCA|\\uD83C\\uDCCB|\\uD83C\\uDCCD|\\uD83C\\uDCCE){5}"

        );
        public static Pattern straightRegExp = Pattern.compile(
                "(\\uD83C\\uDCA1|\\uD83C\\uDCB1|\\uD83C\\uDCD1|\\uD83C\\uDCC1)(\\uD83C\\uDCAE|\\uD83C\\uDCBE|\\uD83C\\uDCDE|\\uD83C\\uDCCE)(\\uD83C\\uDCAD|\\uD83C\\uDCBD|\\uD83C\\uDCDD|\\uD83C\\uDCCD)(\\uD83C\\uDCAB|\\uD83C\\uDCBB|\\uD83C\\uDCDB|\\uD83C\\uDCCB)(\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA)|" +
                        "(\\uD83C\\uDCAE|\\uD83C\\uDCBE|\\uD83C\\uDCDE|\\uD83C\\uDCCE)(\\uD83C\\uDCAD|\\uD83C\\uDCBD|\\uD83C\\uDCDD|\\uD83C\\uDCCD)(\\uD83C\\uDCAB|\\uD83C\\uDCBB|\\uD83C\\uDCDB|\\uD83C\\uDCCB)(\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA)(\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9)|" +
                        "(\\uD83C\\uDCAD|\\uD83C\\uDCBD|\\uD83C\\uDCDD|\\uD83C\\uDCCD)(\\uD83C\\uDCAB|\\uD83C\\uDCBB|\\uD83C\\uDCDB|\\uD83C\\uDCCB)(\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA)(\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9)(\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8)|" +
                        "(\\uD83C\\uDCAB|\\uD83C\\uDCBB|\\uD83C\\uDCDB|\\uD83C\\uDCCB)(\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA)(\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9)(\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8)(\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7)|" +
                        "(\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA)(\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9)(\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8)(\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7)(\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6)|" +
                        "(\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9)(\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8)(\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7)(\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6)(\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5)|" +
                        "(\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8)(\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7)(\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6)(\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5)(\\uD83C\\uDCA4|\\uD83C\\uDCB4|\\uD83C\\uDCD4|\\uD83C\\uDCC4)|" +
                        "(\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7)(\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6)(\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5)(\\uD83C\\uDCA4|\\uD83C\\uDCB4|\\uD83C\\uDCD4|\\uD83C\\uDCC4)(\\uD83C\\uDCA3|\\uD83C\\uDCB3|\\uD83C\\uDCD3|\\uD83C\\uDCC3)|" +
                        "(\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6)(\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5)(\\uD83C\\uDCA4|\\uD83C\\uDCB4|\\uD83C\\uDCD4|\\uD83C\\uDCC4)(\\uD83C\\uDCA3|\\uD83C\\uDCB3|\\uD83C\\uDCD3|\\uD83C\\uDCC3)(\\uD83C\\uDCA2|\\uD83C\\uDCB2|\\uD83C\\uDCD2|\\uD83C\\uDCC2)|" +
                        "(\\uD83C\\uDCA1|\\uD83C\\uDCB1|\\uD83C\\uDCD1|\\uD83C\\uDCC1)(\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5)(\\uD83C\\uDCA4|\\uD83C\\uDCB4|\\uD83C\\uDCD4|\\uD83C\\uDCC4)(\\uD83C\\uDCA3|\\uD83C\\uDCB3|\\uD83C\\uDCD3|\\uD83C\\uDCC3)(\\uD83C\\uDCA2|\\uD83C\\uDCB2|\\uD83C\\uDCD2|\\uD83C\\uDCC2)"
        );
        public static final Pattern ofAKindRegExp = Pattern.compile(
                "(?:\\uD83C\\uDCA1|\\uD83C\\uDCB1|\\uD83C\\uDCD1|\\uD83C\\uDCC1){2,4}|" +
                        "(?:\\uD83C\\uDCAE|\\uD83C\\uDCBE|\\uD83C\\uDCDE|\\uD83C\\uDCCE){2,4}|" +
                        "(?:\\uD83C\\uDCAD|\\uD83C\\uDCBD|\\uD83C\\uDCDD|\\uD83C\\uDCCD){2,4}|" +
                        "(?:\\uD83C\\uDCAB|\\uD83C\\uDCBB|\\uD83C\\uDCDB|\\uD83C\\uDCCB){2,4}|" +
                        "(?:\\uD83C\\uDCAA|\\uD83C\\uDCBA|\\uD83C\\uDCDA|\\uD83C\\uDCCA){2,4}|" +
                        "(?:\\uD83C\\uDCA9|\\uD83C\\uDCB9|\\uD83C\\uDCD9|\\uD83C\\uDCC9){2,4}|" +
                        "(?:\\uD83C\\uDCA8|\\uD83C\\uDCB8|\\uD83C\\uDCD8|\\uD83C\\uDCC8){2,4}|" +
                        "(?:\\uD83C\\uDCA7|\\uD83C\\uDCB7|\\uD83C\\uDCD7|\\uD83C\\uDCC7){2,4}|" +
                        "(?:\\uD83C\\uDCA6|\\uD83C\\uDCB6|\\uD83C\\uDCD6|\\uD83C\\uDCC6){2,4}|" +
                        "(?:\\uD83C\\uDCA5|\\uD83C\\uDCB5|\\uD83C\\uDCD5|\\uD83C\\uDCC5){2,4}|" +
                        "(?:\\uD83C\\uDCA4|\\uD83C\\uDCB4|\\uD83C\\uDCD4|\\uD83C\\uDCC4){2,4}|" +
                        "(?:\\uD83C\\uDCA3|\\uD83C\\uDCB3|\\uD83C\\uDCD3|\\uD83C\\uDCC3){2,4}|" +
                        "(?:\\uD83C\\uDCA2|\\uD83C\\uDCB2|\\uD83C\\uDCD2|\\uD83C\\uDCC2){2,4}"
        );

        private static int royalFlush = UNIPOKER_0X900000;
        private static int straightFlush = UNIPOKER_0X800000;
        private static int fourOfAKind = UNIPOKER_0X700000;
        private static int fullHouse = UNIPOKER_0X600000;
        private static int flush = UNIPOKER_0X500000;
        private static int straight = UNIPOKER_0X400000;
        private static int threeOfAKind = UNIPOKER_0X300000;
        private static int twoPair = UNIPOKER_0X200000;
        private static int pair = UNIPOKER_0X100000;
    }

    public static class Player extends Hand {
        private String mName;
        private int mWins;
        private int[] mHandTypeCounts;
		/*
   		 * @State
  		 */
        Player(String name) {
            super();
            this.mWins = 0;
            this.mName = name;
            this.mHandTypeCounts = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        }
  // scoreHand
        public void scoreHand() {
            this.score();
            int handType = this.getRank() >> UNIPOKER_20;
            this.handTypeCounts()[handType] += 1;
        }

        public void wonHand() {
            this.mWins += 1;
        }
  // name
        public String name() {
            return this.mName;
        }
  // hand
        public String hand() {
            return super.toString();
        }
  // wins
        public int wins() {
            return this.mWins;
        }
  // handTypeCounts
        public int[] handTypeCounts() {
            return this.mHandTypeCounts;
        }
    }

    public static void playHands(List<Player> players) {
        CardDeck cardDeck = new CardDeck();
        int handsPlayed = 0;
        int highestRank = 0;
  // begin do while
        do {
            cardDeck.shuffle();
            for (Player player : players) {
				// clear
                player.clear();
            }
            for (int i = 0; i < UNIPOKER_5; i++) {
                for (Player player : players) {
                    player.takeCard(cardDeck.dealOneCard());
                }
            }

            for (Player player : players) {
                player.scoreHand();
            }
            handsPlayed += 1;
    		// reset highestRank
            highestRank = 0;
    		// check highestRank
            for (Player player : players) {
                if (player.getRank() > highestRank) {
                    highestRank = player.getRank();
                }
            }

            for (Player player : players) {
                // We count ties as wins for each player.
                if (player.getRank() == highestRank) {
                    player.wonHand();
                }
            }
        } while (handsPlayed < UNIPOKER_2000);
    }
}


/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    public ArrayList<UniPoker.Player> players = new ArrayList<>();
	public static void main(String[] args) {
        run(false);
    }
    /*
     * @State
     */
    Benchmark() {
        this.players.add(new UniPoker.Player("Player 1"));
        this.players.add(new UniPoker.Player("Player 2"));
        this.players.add(new UniPoker.Player("Player 3"));
        this.players.add(new UniPoker.Player("Player 4"));
    }

    public void runIteration() {
        UniPoker.playHands(this.players);
    }

    public void validate() {
        boolean isInBrowser = true;
        if (this.players.size() != UniPoker.playerExpectations.size()) {
            throw new RuntimeException("Expect " + UniPoker.playerExpectations.size() + ", but actually have " + this.players.size());
        }

        if (isInBrowser) {
            for (int playerIdx = 0; playerIdx < UniPoker.playerExpectations.size(); playerIdx++) {
                UniPoker.playerExpectations.get(playerIdx).validate(this.players.get(playerIdx));
            }
        }
    }
    /*
     * @Benchmark
     */
    public static void run(boolean logsEnabled) {
        Benchmark benchmark = new Benchmark();
        long startTime = UniPoker.ArkTools.timeInUs();
        int[] results = new int[0];
        for (int i = 0; i < UniPoker.UNIPOKER_120; i++) {
            benchmark.runIteration();
        }
        long endTime = UniPoker.ArkTools.timeInUs();
        long time =  (endTime - startTime);
        System.out.println("uni-poker: ms = " + time / 1_000_000.0);
        if (logsEnabled) {
            for (int index = 0; index < benchmark.players.size(); index++ ) {
                System.out.println("players " + index + " wins:" + benchmark.players.get(index).wins() + " handTypeCounts:" + Arrays.toString(benchmark.players.get(index).handTypeCounts()));
            }
        }
    }
}

